﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using StockFinder.Model;
using log4net;

namespace StockFinder.Services.Indicators
{
    public class PriceAnalyser
    {
        private static readonly ILog _Log = LogManager.GetLogger(typeof(PriceAnalyser));

        public static bool Validate(Symbol symbol)
        {
            bool isValid = true;

            if (symbol == null)
            {
                isValid = false;
            }

            if (symbol.Prices.Count == 0)
            {
                isValid = false;
            }

            return isValid;
        }

        public static decimal AllTimeHighAdjustedClosePrice(IOrderedEnumerable<DailyPrice> prices)
        {
            decimal highestClose = 0;

            if (prices.Count() > 0)
            {
                highestClose = prices.Max(price => price.AdjustedClose);
            }

            return highestClose;
        }

        public static DailyPrice AllTimeHighestPriceByAdjustedClose(IOrderedEnumerable<DailyPrice> prices)
        {
            DailyPrice highestClose = null;

            if (prices.Count() > 0)
            {
                decimal allTimeHighPrice = prices.Max(price => price.AdjustedClose);

                highestClose = prices.First(price => price.AdjustedClose == allTimeHighPrice);
            }

            return highestClose;
        }



        public static DailyPrice AllTimeHighestPriceByHigh(IOrderedEnumerable<DailyPrice> prices)
        {
            DailyPrice highest = null;

            if (prices.Count() > 0)
            {
                decimal allTimeHighPrice = prices.Max(price => price.High);

                highest = prices.First(price => price.High == allTimeHighPrice);
            }

            return highest;
        }

        public static bool IsAtAllTimeHigh(IOrderedEnumerable<DailyPrice> prices)
        {
            bool isAtAllTimeHigh = false;

            if (prices.Count() > 0)
            {
                //compare latest price against highest price
                isAtAllTimeHigh = (AllTimeHighestPriceByAdjustedClose(prices).AdjustedClose == prices.ElementAt(0).AdjustedClose);
            }

            return isAtAllTimeHigh;
        }

        public static bool IsWithinBreakoutRange(IOrderedEnumerable<DailyPrice> prices, int percentage, int days)
        {
            bool isWithinBreakoutRange = false;

            DailyPrice allTimeHighestClose = AllTimeHighestPriceByAdjustedClose(prices);

            DailyPrice lastPrice = prices.ElementAt(0);

            _Log.DebugFormat("Highest close: {0} {1}", allTimeHighestClose.AdjustedClose, allTimeHighestClose.PriceDate);

            _Log.DebugFormat("Last close: {0} {1}", lastPrice.AdjustedClose, lastPrice.PriceDate);

            //below highest close - i.e. in a base
            if (lastPrice.AdjustedClose < allTimeHighestClose.AdjustedClose)
            {
                //work out how long been in base
                TimeSpan timeSinceAllTimeHigh = lastPrice.PriceDate - allTimeHighestClose.PriceDate;

                _Log.DebugFormat("Time in base: {0}", timeSinceAllTimeHigh.Days);

                if (timeSinceAllTimeHigh.Days >= days)
                {                   
                    decimal priceDifference = allTimeHighestClose.AdjustedClose - lastPrice.AdjustedClose;

                    _Log.DebugFormat("Price difference: {0}", priceDifference);

                    decimal percentageDifference = ((priceDifference / allTimeHighestClose.AdjustedClose) * 100);

                    _Log.DebugFormat("Price percent difference: {0}", percentageDifference);

                    if (percentageDifference < percentage)
                    {
                        isWithinBreakoutRange = true;
                    }
                    else
                    {
                        _Log.DebugFormat("Failed: Percent not within range");
                    }
                }
                else
                {
                    _Log.DebugFormat("Failed: Base is not long enough");
                }
            }
            else
            {
                _Log.DebugFormat("Failed: last close not less than all time high");
            }

            return isWithinBreakoutRange;
        }

        /// <summary>
        /// Determines whether the stock's latest close is a breakout from a specificed base
        /// </summary>
        /// <param name="prices"></param>
        /// <param name="baseLength"></param>
        /// <returns></returns>
        public static bool HasJustBrokenOut(IOrderedEnumerable<DailyPrice> prices, int baseLength)
        {
            /*
             * Price has just broken out if the last price is new all time high, penultimate price
             * is below a previous all time high that occurred in a range longer than the base length
             * parameter.
             * 
             */

            bool hasJustBrokenOut = false;

            //make sure there are enough prices
            if (prices.Count() > baseLength)
            {
                //get last price
                DailyPrice allTimeHighestClose = AllTimeHighestPriceByAdjustedClose(prices);
                
                //last price
                DailyPrice lastPrice = prices.ElementAt(0);               

                //last price is the all time high
                if (allTimeHighestClose.PriceDate == lastPrice.PriceDate)
                {
                    //set 2, previous base - work out if was in base
                    IOrderedEnumerable<DailyPrice> previousBasePrices = prices.Skip(1).OrderByDescending(price => price.PriceDate);

                    //all time high from previous base
                    DailyPrice previousBaseHigh = AllTimeHighestPriceByAdjustedClose(previousBasePrices);

                    //previous base's last price
                    DailyPrice previousBaseLastPrice = previousBasePrices.ElementAt(0);

                    //base length
                    TimeSpan previousBaseLength = previousBaseLastPrice.PriceDate - previousBaseHigh.PriceDate;

                    if (previousBaseLength.TotalDays >= baseLength)
                    {
                        hasJustBrokenOut = true;
                    }
                }
            }           

            return hasJustBrokenOut;
        }

        public static bool IsCloseToAllTimeHigh(IOrderedEnumerable<DailyPrice> prices, int percentage, int days)
        {
            bool isCloseToAllTimeHigh = false;
            
            //find all time high
            decimal highestClose = 0;
            DateTime highestCloseDate = DateTime.MinValue;
            DailyPrice highestClosePrice = null;

            if (prices.Count() > 0)
            {
                //find highest price
                foreach (var price in prices)
                {
                    if (price.AdjustedClose > highestClose)
                    {
                        highestClose = price.AdjustedClose;

                        highestCloseDate = price.PriceDate;

                        highestClosePrice = price;
                    }
                }           
            }

            //is within % of all time high
            if (highestClosePrice != null)
            {               
                DailyPrice lastPrice = prices.ElementAt(0);

                _Log.DebugFormat("Highest close: {0} {1}", highestClosePrice.AdjustedClose, highestClosePrice.PriceDate);

                _Log.DebugFormat("Last close: {0} {1}", lastPrice.AdjustedClose, lastPrice.PriceDate);

                //is the recent price less than ATH?
                if (lastPrice.AdjustedClose < highestClosePrice.AdjustedClose)
                {
                    _Log.DebugFormat("Last price is lower than all time high");

                    //is the recent close within % of ATH
                    decimal currentPercentageFromATH = ((highestClosePrice.AdjustedClose - lastPrice.AdjustedClose) / highestClosePrice.AdjustedClose) * 100;

                    _Log.DebugFormat("Percent from ATH: {0}", currentPercentageFromATH);

                    if (currentPercentageFromATH <= percentage)
                    {
                        _Log.DebugFormat("Is within percent range");

                        TimeSpan timeDifference = lastPrice.PriceDate - highestClosePrice.PriceDate;

                        _Log.DebugFormat("Time in base: {0}", timeDifference.Days);

                        if (timeDifference.Days >= days)
                        {
                            _Log.DebugFormat("Base is long enough");

                            isCloseToAllTimeHigh = true;
                        }
                    }
                }
            }
            
            //is at or further than days

            return isCloseToAllTimeHigh;
        }
    }
}
